package aino;

import java.io.*;
import java.util.*;

public class Class {
    String name = null;
    String outer = null;
    String inner = null;
    String sourceFile = null;
    int staticLen = 0;
    int instanceLen = 0;
    int startAddr;

    int count;
    Pool[] pool;

    int fieldsCount;
    Field[] fields;

    int methodsCount;
    Method[] methods;

    int attributesCount;

    Vector sourceLines;

    private int magic;                             // magic field of class file 0xcafebabe
    private int minorVersion;                      // compiler minor version number
    private int majorVersion;                      // compiler major version number

    private int accessFlags;
    private int thisClass;
    private int superClass;
    private int interfacesCount;

    public Class(String nam, String out, String in, int flags) {
	name = nam;
	inner = in;
	outer = out;
	accessFlags = flags;
    }

    public Class(String filename) {
	staticLen = 0;
	instanceLen = 0;

	//System.out.println("File = "+filename);
	DataInputStream ios = null;
	File f = new File(filename);
	try {
	    if (f.isFile()) {
		ios = new DataInputStream(new FileInputStream(f));
	    }
	    if (ios == null) throw new FileNotFoundException(filename);

	    // read the magic bytes - abort if not a class file
	    magic = ios.readInt();
	    if (magic != 0xcafebabe) throw new IOException("File is not a java class file.");

	    // read the compiler version
	    minorVersion = ios.readShort();
	    majorVersion = ios.readShort();

	    // Log.out("major="+majorVersion+" minor="+minorVersion);

	    count = ios.readShort();

	    pool = new Pool[count];

	    Log.out("pool count="+count);
	    for (int i = 1; i < count; i++) {
		int tag = ios.readByte();
		Log.out("|"+i+": ");
		switch (tag) {
		case 7: // class: name index
		case 8: // string: string index
		    pool[i] = new Pool(tag, ios.readShort(), 0, null);
		    Log.out("tag "+tag+" = "+pool[i].value1);
		    break;

		case 9:  // field ref: class index, name and type index
		case 10: // method ref: class index, name and type index
		case 11: // interface method ref: class index, name and type index
		case 12: // name and type: name index, descriptor index
		    pool[i] = new Pool(tag, ios.readShort(), ios.readShort(), null);
		    Log.out("tag "+tag+" = "+pool[i].value1+"."+pool[i].value2);
		    break;

		case 3: // integer: bytes
		case 4: // float: bytes
		    pool[i] = new Pool(tag, ios.readInt(), 0, null);
		    Log.out("tag "+tag+" = "+pool[i].value1);
		    break;

		case 5: // long: high bytes, low bytes
		case 6: // double: high bytes, low bytes
		    pool[i] = new Pool(tag, ios.readInt(), ios.readInt(), null);
		    Log.out("tag "+tag+" = "+pool[i].value1+"."+pool[i].value2);
		    i++; // longs take two slots from the constant pool
		    break;

		case 1: // utf8: length, bytes
		    pool[i] = new Pool(tag, 0, 0, readUtf8(ios));
		    Log.out("tag "+tag+" = "+pool[i].str);
		    break;

		default:
		    Log.out("UNKNOWN TAG "+tag+" at pos "+i);
		    System.exit(1);
		}
	    } // for

	    accessFlags = ios.readShort();
	    thisClass = ios.readShort();
	    superClass  = ios.readShort();

	    name = getConst(thisClass);

	    Log.out("Access flags = "+accessFlags+"\nClass name = "+
			       getConst(thisClass)+"\nSuper class = "+
			       getConst(superClass));

	    interfacesCount = ios.readShort();
	    Log.out("Interfaces count = "+interfacesCount);
	    for (int i = 0; i < interfacesCount; i++) {
	      int tmp = ios.readShort();
	      Log.out("  "+getConst(tmp));
	    }

	    fieldsCount = ios.readShort();
	    fields = new Field[fieldsCount];
	    Log.out("Fields count = "+fieldsCount);
	    for (int i = 0; i < fieldsCount; i++) {
		int access_flags = ios.readShort();
		int name_index = ios.readShort();
		int descriptor_index = ios.readShort();
		int attributes_count = ios.readShort();
		Log.out("  "+getConst(name_index)+" "+getConst(descriptor_index));
		Log.out("    Attributes count = "+attributes_count);
		Field fi = new Field(access_flags, name_index, descriptor_index, attributes_count);
		for (int j = 0; j < attributes_count; j++) {
		    int ni = ios.readShort();
		    int al = ios.readInt();
		    Attribute a = new Attribute(ni, al, this);
		    for (int k = 0; k < al; k++) {
			byte x = ios.readByte();
			a.addInfo(k, x);
		    }
		    fi.addAttribute(j, a);
		    //Log.out("      "+getConst(a.getConstantValue()));
		}
		fields[i] = fi;
	    }

	    methodsCount = ios.readShort();
	    methods = new Method[methodsCount];
	    Log.out("Methods count = "+methodsCount);
	    for (int i = 0; i < methodsCount; i++) {
		int access_flags = ios.readShort();
		int name_index = ios.readShort();
		int descriptor_index = ios.readShort();
		int attributes_count = ios.readShort();
		Log.out("  "+getConst(name_index)+" "+getConst(descriptor_index));
		Log.out("    Attributes count = "+attributes_count);
		Method me = new Method(this, access_flags, name_index, descriptor_index, attributes_count);
		if (getConst(name_index).equals("main"))
		   Aino.mainName = name;

		for (int j = 0; j < attributes_count; j++) {
		    int ni = ios.readShort();
		    int al = ios.readInt();
		    Log.out("      attrname = "+getConst(ni));
		    Attribute a = new Attribute(ni, al, this);
		    for (int k = 0; k < al; k++) {
			byte x = ios.readByte();
			a.addInfo(k, x);
		    }
		    me.addAttribute(j, a);
		}
		methods[i] = me;
		//me.printCodes();
	    }

	    attributesCount = ios.readShort();
	    Log.out("Attributes count = "+attributesCount);
	    for (int i = 0; i < attributesCount; i++) {
		int nameIndex = ios.readShort();
		int length = ios.readInt();
		String attrName = getConst(nameIndex);
		Log.out("  Attr = "+attrName);
		if (attrName.equals("InnerClasses")) {
		    int numOfClasses = ios.readShort();
		    for (int j = 0; j < numOfClasses; j++) {
			int in_c_info_idx = ios.readShort();
			int out_c_info_idx = ios.readShort();
			int in_name_idx = ios.readShort();
			int in_c_acc_flags = ios.readShort();
			Class c =  new Class(getConst(in_c_info_idx), 
					     getConst(out_c_info_idx), 
					     getConst(in_name_idx), 
					     in_c_acc_flags);
			//Aino.classes.add(c);
			Log.out("Inner class "+getConst(in_c_info_idx)+
				", out: "+getConst(out_c_info_idx)+
				", name: "+getConst(in_name_idx)+
				", flags: "+in_c_acc_flags);
		    }
		} else if (attrName.equals("SourceFile")) {
		    int fnidx = ios.readShort();
		    sourceFile = getConst(fnidx);
		} else {
		    for (int j = 0; j < length; j++) ios.readByte();
		}
	    }

	} catch (FileNotFoundException e) {
	    // report the error
	    System.out.println(e);

	} catch (IOException e) {
	    // report the error
	    System.out.println(e);
	}

	readSourceFile();
    }

    public void calcStuff() {
	// fields
	for (int i = 0; i < fieldsCount; i++) {
	    Field f = fields[i];
	    int varlen;
	    String descr = getConst(f.descriptorIndex);

	    if (descr.equals("J")) varlen = 2;
	    else varlen = 1;

	    if ((f.accessFlags & 0x0010) == 0) { // FINALE?
		if ((f.accessFlags & 0x0008) == 0) { // STATIC?
		    // instance variable
		    f.address = instanceLen;
		    instanceLen += varlen;
		} else {
		    // static
		    f.address = staticLen;
		    staticLen += varlen;
		}
	    }
	}

	for (int i = 0; i < methodsCount; i++) {
	    Method m = methods[i];
	    String descr = getConst(m.descriptorIndex);

	    // find arguments
	    int j;
	    if ((j = descr.indexOf('(')) >= 0) {
		int argnum;
		if ((m.accessFlags & 0x0008) == 0) argnum = 1;
		else argnum = 0; // static method

		j++;
		while (descr.charAt(j) != ')') {
		    if (descr.charAt(j) == 'L') {
			int x;
			for (x = j; descr.charAt(x) != ';'; x++) ;
			j = x + 1;
			argnum++;
		    } else if (descr.charAt(j) == '[') {
			j++;
		    } else {
			j++;
			argnum++;
		    }
		}
		m.args = argnum;
	    } else {
		System.out.println("\n***Error: Method has no descriptor!: "+descr);
		System.exit(1);
	    }

	    // find called functions
	    int pc = 0;
	    int codelen = m.getCodeLength();
	    int bipush1 = 0, bipush2 = 0, idx = 0;
	    while (pc < codelen) {
		int c = m.getCodeByte(pc);
		if (c == 0x68 || c == 0x69) Aino.mul = true;
		else if (c == 0x6c || c == 0x6d) Aino.div = true;
		else if (c == 0x70 || c == 0x71) Aino.mod = true;
		else if (c == 0xb6 || c == 0xb7 || c == 0xb8) {
		    int meth = (m.getCodeByte(pc+1)<<8)|m.getCodeByte(pc+2);
		    check(meth);
		    if (pool[meth].tag != 10) {
			System.out.println("\n***Error: Something not a methodref called!");
			System.exit(1);
		    }
		    String className = getConst(pool[meth].value1);
		    String funcName = getConst(pool[pool[meth].value2].value1);
		    String argsName = getConst(pool[pool[meth].value2].value2);
		    Log.out("Method "+getConst(m.nameIndex));
		    Log.out(" calls "+className+"."+funcName+" "+argsName);

		    Method called = Aino.findMethod(className, funcName, argsName);
		    if (funcName.equals("_memory")) {
			for (int k = bipush2; k < bipush1; k++)
			    Aino.freeMemory[k] = true;
			if (Aino.memStart > bipush2) Aino.memStart = bipush2;
			if (Aino.memEnd < bipush1) Aino.memEnd = bipush1;
		    } else if (funcName.equals("_clock")) {
			Aino.clockSpeed =
			    Integer.decode(getConst(idx)).intValue();
		    } else if (funcName.equals("_banksize")) {
			Aino.bankSize = bipush1;
		    } else if (funcName.equals("_heap")) {
			Aino.heap = bipush1;
		    } else if (funcName.equals("_initline")) {
			check(idx);
			if (m.cls.pool[idx].tag == 8)
			    Aino.initlines.add(m.cls.getConst(idx));
			else 
			    System.out.println("\n*** Error: Wrong argument for processor");
		    } else if (called != null) {
			m.calls.add(called);
		    } else {
			// System.out.println("Unknown method "+className+"."+funcName+" called");
		    }
		} else if (c == 0x10) { // bipush
		    bipush2 = bipush1;
		    bipush1 = m.getCodeByte(pc+1);
		} else if (c == 0x11) { // sipush
		    bipush2 = bipush1;
		    bipush1 = m.getCodeShort(pc+1);
		} else if (c == 0x12) {
		    idx = m.getCodeByte(pc+1);
		} else if (c == 0x13) {
		    idx = m.getCodeShort(pc+1);
		}
		pc = m.getNextPC(pc);
	    }
	}
    }

    public String readUtf8( DataInputStream dStream ) {
	StringBuffer strBuf = null;
	int len, charCnt;
	byte one_byte;
	char one_char;

	strBuf = new StringBuffer();
	one_char = '\u0000';

	try {
	    len = dStream.readShort();
	    charCnt = 0;
	    while (charCnt < len) {
		one_byte = (byte)dStream.readByte();
		charCnt++;
		if ((one_byte >> 7) == 1) {
		    short tmp;
		    Log.out("Warning! Multibyte character!");
		    // its a multi-byte character
		    tmp = (short)(one_byte & 0x3f);  // Bits 5..0 (six bits)
		    // read the next byte
		    one_byte = (byte)dStream.readByte();
		    charCnt++;
		    tmp = (short)(tmp | ((one_byte & 0x3f) << 6));
		    if ((one_byte >> 6) == 0x2) {
			// We have 12 bits so far, get bits 15..12
			one_byte = (byte)dStream.readByte();
			charCnt++;
			one_byte = (byte)(one_byte & 0xf);
			tmp = (short)(tmp | (one_byte << 12));
		    }
		    one_char = (char)tmp;
		}
		else {
		    one_char = (char)one_byte;
		}
		strBuf.append(one_char);
	    } // while
	} catch (Exception e) {
	}
	return strBuf.toString();
    } // read

    String getSuperClass() {
	return getConst(superClass);
    }

    String getConst(int i) {
	if (i == 0) return null;
	check(i);
	switch (pool[i].tag) {
	case 7:
	case 8:
	    return getConst(pool[i].value1);

	case 9:
	case 10:
	case 11:
	case 12:
	    return getConst(pool[i].value1)+" & "+getConst(pool[i].value2);

	case 3:
	    return new String(""+pool[i].value1);

	case 4:
	    return "float";

	case 5:
	    return new String(""+((pool[i].value1 << 32)|pool[i].value2));

	case 6:
	    return "double";

	case 1:
	    return pool[i].str;
	}
	return null;
    }

    void print() {
	for (int i = 1; i < count; i++)
	    System.out.println(getConst(i));
    }

    String getClassName(int index) {
	check(index);
	int ci = pool[index].value1;
	check(ci);
	int cni = pool[ci].value1;
	check(cni);
	return Aino.fixStr(pool[cni].str);
    }

    String getName(int index) {
	check(index);
	int nti = pool[index].value2;
	check(nti);
	int ni = pool[nti].value1;
	check(ni);
	return Aino.fixStr(pool[ni].str);
    }

    String getDescriptor(int index) {
	check(index);
	int nti = pool[index].value2;
	check(nti);
	int di = pool[nti].value2;
	check(di);
	return pool[di].str;
    }

    String getValidFuncName(int index) {
	String c = getClassName(index);
	String f = getName(index);
	String a = getDescriptor(index);
	String fname = c+"_"+f+Aino.fixStr(a);
	//String fname = c+"_"+Aino.fixStr(f)+Aino.fixStr(a);
	return fname;
    }

    String getValidVarName(int index) {
	String c = getClassName(index);
	String f = getName(index);
	String fname = c+"_"+f;
	return fname;
    }

    void check(int x) {
	if (x <= 0 || x >= count) {
	    System.out.println("\n*** Internal error: Pool index %d!");
	    Exception err = new Exception();
	    err.printStackTrace();
	    System.out.flush();
	    System.exit(6);
	}
    }


    void readSourceFile() {
	sourceLines = new Vector();
	File sf = new File(sourceFile);
	try {
	    FileReader ip;
	    if (sf.isFile()) {
		char[] buf = new char[80];
		int len = 0;
		ip = new FileReader(sf);
		while (ip.ready()) {
		    char ch = (char)ip.read();
		    if (ch == '\n') {
			sourceLines.add(new String(buf, 0, len));
			len = 0;
		    } else if (ch == '\r') {
			// ignore
		    } else if (len < 79) {
			buf[len] = ch;
			len++;
		    }
		}
		ip.close();
	    }
	} catch (Exception e) {
	}
    }

    /***
    public Class(String n, int c, int i) {
	name = n;
	staticLen = c;
	instanceLen = i;
    }
    ***/
}
